package org.wiw.client;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.wiw.client.gui.AddMarkerDialog;
import org.wiw.client.gui.IDialogHandler;
import org.wiw.client.gui.MarkersFilterControl;
import org.wiw.client.gui.MessageDialog;
import org.wiw.client.model.Person;
import org.wiw.client.model.Place;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.maps.client.InfoWindowContent;
import com.google.gwt.maps.client.MapUIOptions;
import com.google.gwt.maps.client.MapWidget;
import com.google.gwt.maps.client.event.MapRightClickHandler;
import com.google.gwt.maps.client.event.MarkerClickHandler;
import com.google.gwt.maps.client.event.MarkerDragEndHandler;
import com.google.gwt.maps.client.geocode.Geocoder;
import com.google.gwt.maps.client.geocode.LocationCallback;
import com.google.gwt.maps.client.geocode.Placemark;
import com.google.gwt.maps.client.geom.LatLng;
import com.google.gwt.maps.client.geom.Size;
import com.google.gwt.maps.client.impl.MapEvent;
import com.google.gwt.maps.client.overlay.Icon;
import com.google.gwt.maps.client.overlay.Marker;
import com.google.gwt.maps.client.overlay.MarkerOptions;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.MenuBar;
import com.google.gwt.user.client.ui.MenuItem;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;

/**
 * Map entry point
 */
public class MapEntryPoint implements EntryPoint {
	/**
	 * Create a remote service proxy to talk to the server-side service.
	 */
	private final MapServiceAsync mapService = GWT.create(MapService.class);
	
	private Person person = null;
	
	private MarkerPlaceStorage storage = new MarkerPlaceStorage();
	
	private Set<Marker> myMarkers = new HashSet<Marker>();
	
	private PopupPanel popup;

	private MapWidget map;

	/**
	 * This is the entry point method.
	 */
	public void onModuleLoad(){
		setProgressMessage("Loading user data...");
		// get person
	    mapService.getCurrentUser(new AsyncCallback<Person>() {
			public void onSuccess(Person p) {
				person = p;
				initialize();			}
			public void onFailure(Throwable caught) {}
		});
	}
	
	protected void setProgressMessage(String message){
		RootPanel.get("map").clear();
		RootPanel.get("map").add(new HTML("<center>"+message+"</center>"));
	}
	
	/**
	 * Initialize
	 */
	private void initialize() {
		setProgressMessage("Initialize map...");
		
		popup = new PopupPanel(true, false);
		
		map = new MapWidget(LatLng.newInstance(32.176257,34.888877), 3);
		// capture all parent div
	    map.setSize("100%", "100%");
	    // enable search widget
	    map.setGoogleBarEnabled(true);
	    map.setUI(getMapUIOptions());
	    map.addControl(new MarkersFilterControl(storage));
	    		
	    // popup menu
	    map.addMapRightClickHandler(new MapRightClickHandler() {
			public void onRightClick(final MapRightClickEvent event) {
				final LatLng location = map.convertContainerPixelToLatLng(event.getPoint());
				final Marker marker = (Marker)event.getOverlay();
				final Place markerPlace = storage.get(marker);
				popup.clear();
				
				MenuBar popupMenuBar = new MenuBar(true);
				MenuItem addItem = new MenuItem("Add place", true, 
						new Command() {
							public void execute() {
								popup.hide();
								final AddMarkerDialog dialog = new AddMarkerDialog();
								dialog.setHandler(new IDialogHandler() {
									public void okPressed() {
										mapService.addPlace(
												location.getLatitude(), location.getLongitude(), person, dialog.getWhen(), dialog.getDescription(),
												new AsyncCallback<Place>() {
											public void onSuccess(Place place) {
												createMarker(place, map);
											}
											public void onFailure(Throwable caught) {
												MessageDialog.showErrorDialog("Error on update", 
														"Failed to add new place: " + caught.getMessage());
											}
										});
									}
									public void cancelPressed() {}
								});
								dialog.center();
							}
						});
				MenuItem removeItem = new MenuItem("Remove place", true, 
						new Command() {
							public void execute() {
								popup.hide();
								if(markerPlace == null){
									MessageDialog.showErrorDialog("Error on update","Can't find place by overlay");
								}
								else{
									mapService.removePlace(markerPlace.getId(), new AsyncCallback<Void>() {
										public void onSuccess(Void result) {
											removeMarker(marker);
										}
										public void onFailure(Throwable caught) {
											MessageDialog.showErrorDialog("Error on remove", 
													"Failed to remove place: " + caught.getMessage());
										}
									});
								}
							}
						});
				MenuItem checkItem = new MenuItem("What is here?", true, 
						new Command() {
							public void execute() {
								popup.hide();
								new Geocoder().getLocations(location, new LocationCallback() {
									public void onSuccess(JsArray<Placemark> locations) {
										map.getInfoWindow().open(
												location, 
										        new InfoWindowContent(locations.get(0).getAddress()));
									}
									public void onFailure(int statusCode) {
									}
								}); 
							}
						});

				if(marker == null){
					popupMenuBar.addItem(addItem);
				}
				else if(markerPlace != null && isPermittedOperation(markerPlace.getPerson())){				
					popupMenuBar.addItem(removeItem);
				}
				popupMenuBar.addItem(checkItem);
				popupMenuBar.setVisible(true);
				popup.add(popupMenuBar);
				
				popup.setPopupPositionAndShow(new PositionCallback() {
					public void setPosition(int offsetWidth, int offsetHeight) {
						popup.setPopupPosition(event.getPoint().getX() - 10, event.getPoint().getY() + offsetHeight/2);
					}
				});
												
				//Timer timer = new Timer() { public void run() {popup.hide();} };
				//timer.schedule(3000);
			}
		});
	    
	    RootPanel.get("map").clear();
	    RootPanel.get("map").add(map);
	    
	    // add controls
	    HorizontalPanel controlsPanel = new HorizontalPanel();
	    controlsPanel.setSpacing(2);
	    // current marker image
	    final Image colorImage = new Image(getMarkerImageURL(person.getColor()));
	    controlsPanel.add(colorImage);
	    // markers types
	    final ListBox colors = new ListBox();
	    colors.addItem("Red", "red");
	    colors.addItem("Green", "green");
	    colors.addItem("Blue", "blue");
	    colors.addItem("Yellow", "yellow");
	    colors.addItem("Purple", "purple");
	    colors.addItem("Orange", "orange");
	    colors.addItem("Black", "black");
	    colors.addItem("White", "white");
	    colors.addItem("Gray", "gray");
	    // choose marker
	    for(int i=0;i<colors.getItemCount();i++){
			if(person.getColor().equals(colors.getValue(i))){
	    		colors.setSelectedIndex(i);
	    		break;
	    	}
	    }
	    // change person color	    
	    colors.addChangeHandler(new ChangeHandler() {
	    	public void onChange(ChangeEvent event) {
	    		final String color = colors.getValue(colors.getSelectedIndex());
				mapService.updatePerson(person.getId(), color, 
						new AsyncCallback<Void>() {
							public void onSuccess(Void result) {
								person.setColor(color);
								colorImage.setUrl(getMarkerImageURL(color));
								for(Marker m : myMarkers.toArray(new Marker[0])){
									Place place = storage.get(m);
									// update on person place too
									place.getPerson().setColor(color);
									// re-create marker
									removeMarker(m);
									createMarker(place, map);
								}
							}
							public void onFailure(Throwable caught) {
								MessageDialog.showErrorDialog("Error on update", 
										"Failed to update your data: " + caught.getMessage());
							}
				});
			}
		});
	    
	    controlsPanel.add(colors);
	    
	    // initialize map
	    Button refreshButton = new Button("Refresh");
	    refreshButton.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				initializeMarkers(map);
			}
		});
	    
	    RootPanel.get("controls").add(controlsPanel);
	    
	    // final step
	    initializeMarkers(map);
	}

	private MapUIOptions getMapUIOptions() {
		MapUIOptions options = MapUIOptions.newInstance(Size.newInstance(400, 400));
		options.setScrollwheel(true);
		options.setLargeMapControl3d(true);
		options.setHybridMapType(false);
		options.setSatelliteMapType(true);
		options.setPhysicalMapType(true);
		return options;
	}
	
	private void initializeMarkers(final MapWidget map) {
		map.clearOverlays();
		storage.clear();
	    mapService.getAllPlaces(new AsyncCallback<List<Place>>() {
			public void onSuccess(List<Place> result) {
				for(Place place : result){
					createMarker(place, map);
				}
			}
			public void onFailure(Throwable caught) {
				MessageDialog.showErrorDialog("Error on map fill", 
						"Failed to fetch data: " + caught.getMessage());
			}
		});
	}
	
	public Marker createMarker(final Place place, final MapWidget map) {
		MarkerOptions opt = MarkerOptions.newInstance();
		Icon icon = Icon.newInstance(getMarkerImageURL(place.getPerson().getColor()));
		
		if(place.getWhen() != null && !place.getWhen().isEmpty()){
		    opt.setTitle(place.getPerson().getNickname() + " " + place.getWhen());
		}
		else{
		    opt.setTitle(place.getPerson().getNickname());
		}
		// person can drag his markers only
		if(isPermittedOperation(place.getPerson())){
		    opt.setDraggable(true);
		}
		opt.setIcon(icon);
		
		Marker marker = new Marker(LatLng.newInstance(place.getLatitude(), place.getLongitude()), opt);
		
		marker.addMarkerClickHandler(new MarkerClickHandler() {
			public void onClick(MarkerClickEvent event) {
				map.getInfoWindow().open(event.getSender().getLatLng(), 
						new InfoWindowContent(createPlaceInfo(place, event.getSender())));
			}
		});
		
		if(isPermittedOperation(place.getPerson())){
			marker.addMarkerDragEndHandler(new MarkerDragEndHandler() {
				public void onDragEnd(MarkerDragEndEvent event) {
					final LatLng location = event.getSender().getLatLng();
					mapService.movePlace(place.getId(), location.getLatitude(), location.getLongitude(), 
							new AsyncCallback<Void>() {
					            public void onSuccess(Void result) {
					            	place.setLatitude(location.getLatitude());
					            	place.setLongitude(location.getLongitude());
					            }
					            public void onFailure(Throwable caught) {}
					        });
				}
			});
		}
		// add marker to map
		map.addOverlay(marker);
		// store it locally
		storage.put(marker, place);
		// save as my marker
		if(isCurrentUser(place.getPerson())){
			myMarkers.add(marker);
		}
		
		return marker;
	}
	
	private Widget createPlaceInfo(final Place place, final Marker marker){
		VerticalPanel p = new VerticalPanel();
		p.add(new Label(place.getPerson().getNickname()));
		p.add(new Label(place.getWhen()));
		p.add(new HTML(place.getDescription()));
		if(isPermittedOperation(place.getPerson())){
			Anchor edit = new Anchor("edit");
			edit.addClickHandler(new ClickHandler() {
				public void onClick(ClickEvent event) {
					map.getInfoWindow().close();
					final AddMarkerDialog dialog = new AddMarkerDialog(place);
					dialog.setHandler(new IDialogHandler() {
						public void okPressed() {
							mapService.editPlace(place.getId(), dialog.getWhen(), dialog.getDescription(), 
									new AsyncCallback<Void>() {
										public void onSuccess(Void result) {
											place.setWhen(dialog.getWhen());
											place.setDescription(dialog.getDescription());
										}
										public void onFailure(Throwable caught) {
											MessageDialog.showErrorDialog("Error on update", 
													"Failed to edit new place: " + caught.getMessage());
										}
									});
						}
						public void cancelPressed() {}
					});
					dialog.center();
				}
			});
			p.add(edit);
		}
		return p;
	}
	
	private void removeMarker(Marker marker) {
		map.removeOverlay(marker);
		myMarkers.remove(marker);
		storage.remove(marker);
	}
	
	private boolean isCurrentUser(Person person){
		return this.person.getId() == person.getId();
	}
	
	private boolean isPermittedOperation(Person person){
		return isCurrentUser(person);
	}
	
	private String getMarkerImageURL(String color) {
		return "images/mm_20_"+color+".png";
	}
}
